#pragma once

#ifndef __OGRESUBWIDGET__INCLUDED__HH__
#define __OGRESUBWIDGET__INCLUDED__HH__
 
#include <Ogre.h>
#include <QtGui>
#include "MouseDrag.h"
#include <OgreWidget.h>

class OgreSubWidget;
class OgreSubWidgetListener : public Ogre::FrameListener
{
protected:
	OgreSubWidget* mWidget;
public:
	OgreSubWidgetListener( OgreSubWidget * widget = NULL );
	virtual ~OgreSubWidgetListener();
	
	void setWidget( OgreSubWidget* w );
	OgreSubWidget* widget() const;

	virtual void mouseMoved( const QPoint& pos, Qt::MouseButtons ){}
	virtual void mousePressed( const QPoint& pos, Qt::MouseButtons ){}
	virtual void mouseReleased( const QPoint& pos, Qt::MouseButtons ){}
	virtual void mouseDragged(  MouseDrag & drag  ){}
	virtual void mouseDragging(  MouseDrag & drag  ){}

	virtual void createScene( ){}
	virtual void destroyScene( ){}
	virtual bool frameRenderingQueued( const Ogre::FrameEvent& evt ){return true;}
};

class OgreSubWidget : public QWidget, public Ogre::RenderTargetListener
{
Q_OBJECT
signals:
	void ogreInitialise();
	void ogreShutdown();
	
protected slots:
	void _ogreInitialise(){
		if( !mInitialised )
		{
			try
			{
				initialise();
			}
			catch( std::exception& e )
			{
				QMessageBox::warning( this, "Error", QString("Cannot initialise ogre sub widget because: ")+e.what() );
			}
			mInitialised = true;
			emit ogreInitialise();
			for( auto i=mListeners.begin(); i!=mListeners.end(); i++ ) 
				if( (*i) ){
					(*i)->createScene();
					mRoot->addFrameListener( (*i) );
				}
		}
	}
	void _ogreShutdown(){
		if( mInitialised )
		{
			for( auto i=mListeners.begin(); i!=mListeners.end(); i++ ) 
				if( (*i) ){
					mRoot->removeFrameListener( (*i) );
					(*i)->destroyScene();
				}
			emit ogreShutdown();
			try
			{
				shutdown();
			}
			catch( std::exception& e )
			{
				QMessageBox::warning( this, "Error", QString("Cannot shutdown ogre sub widget because: ")+e.what() );
			}
			mInitialised = false;
		}
	}
	
protected:
	virtual void preViewportUpdate(const Ogre::RenderTargetViewportEvent& evt)
    { (void)evt; }
    virtual void postViewportUpdate(const Ogre::RenderTargetViewportEvent& evt)
    { (void)evt; }

	Ogre::SceneManager *	mSceneManager;
	OgreWidget*				mOgreWidget;
	Ogre::RenderWindow*		mRenderWindow;
	Ogre::Camera*			mCamera;
	Ogre::Viewport*			mViewport;
	Ogre::Root*				mRoot;
	bool					mInitialised;
	MouseDrag				mDrag;

	MouseDrag				mouseDrag() const{ return mDrag; }
	
	std::vector<OgreSubWidgetListener*> mListeners;
public:
	OgreSubWidget( QWidget*parent ) : QWidget(parent), mSceneManager(NULL), mRoot( NULL ), mOgreWidget(NULL), mCamera(NULL), mViewport(NULL), mRenderWindow(NULL), mInitialised(false)
	{
		setAttribute(Qt::WA_NoSystemBackground);
		setAttribute(Qt::WA_PaintOnScreen);
		setMinimumSize(128,128);
		setMouseTracking( true );
	}
	~OgreSubWidget(){}
	
	void addListener( OgreSubWidgetListener * lst ){ mListeners.push_back( lst ); }
	void removeListener( OgreSubWidgetListener * lst ){ for( auto i=mListeners.begin(); i!=mListeners.end(); i++ ) if( (*i) && (*i) == lst ){ mListeners.erase(i); return; } }
 
	void setOgreWidget( OgreWidget* ogre ){ 
		mOgreWidget = ogre; 
		connect( ogre, SIGNAL( ogreInitialise() ), this, SLOT( _ogreInitialise() ) );
		connect( ogre, SIGNAL( ogreShutdown() ), this, SLOT( _ogreShutdown() ) );
	}
	OgreWidget* ogreWidget() const{ return mOgreWidget; }
	
	
	void mousePressEvent( QMouseEvent*evt )
	{
		QPoint p(evt->pos());
		Qt::MouseButtons mb(evt->buttons());
		mDrag.mousePressed( p, mb );
		for( auto i=mListeners.begin(); i!=mListeners.end(); i++ ) 
			if( (*i) )
			{
				(*i)->mousePressed(p,mb);
			}
	}
	void mouseMoveEvent( QMouseEvent*evt )
	{
		QPoint p(evt->pos());
		Qt::MouseButtons mb(evt->buttons());
		mDrag.mouseMoved(p,mb);
		for( auto i=mListeners.begin(); i!=mListeners.end(); i++ ) 
			if( (*i) )
			{
				(*i)->mouseMoved(p,mb);
			}
	}
	void mouseReleaseEvent( QMouseEvent*evt )
	{
		QPoint p(evt->pos());
		Qt::MouseButtons mb(evt->buttons());
		mDrag.mouseReleased(p,mb);
		for( auto i=mListeners.begin(); i!=mListeners.end(); i++ ) 
			if( (*i) )
			{
				(*i)->mouseReleased(p,mb);
			}
	}
	void initialise( )
	{
		if( !mOgreWidget )
			throw std::runtime_error(std::string("main ogre widget not supplied "));
		try
		{
			mRoot = mOgreWidget->ogreRoot();
			mSceneManager = mRoot->createSceneManager(Ogre::ST_GENERIC);
 
			Ogre::NameValuePairList viewConfig;
			Ogre::String widgetHandle;
 
		#ifdef Q_WS_WIN
			widgetHandle = Ogre::StringConverter::toString((size_t)((HWND)winId()));
		#else
			QWidget *q_parent = dynamic_cast <QWidget *> (parent());
			QX11Info xInfo = x11Info();

			widgetHandle = Ogre::StringConverter::toString ((unsigned long)xInfo.display()) +
				":" + Ogre::StringConverter::toString ((unsigned int)xInfo.screen()) +
				":" + Ogre::StringConverter::toString ((unsigned long)q_parent->winId());
		#endif
			viewConfig["externalWindowHandle"] = widgetHandle;
 
		#if defined(Q_WS_WIN)
			//positive integer for W32 (HWND handle) - According to Ogre Docs
			widgetHandle = Ogre::StringConverter::toString((unsigned int)(winId()));
			viewConfig["externalWindowHandle"] = widgetHandle;

		#endif
 
		#if defined(Q_WS_X11)
				//poslong:posint:poslong:poslong (display*:screen:windowHandle:XVisualInfo*) for GLX - According to Ogre Docs
 
			QWidget *q_parent = dynamic_cast <QWidget *> (parent());
			QX11Info info = x11Info();
				widgetHandle  = Ogre::StringConverter::toString((unsigned long)info.display());
				widgetHandle += ":";
				widgetHandle += Ogre::StringConverter::toString((unsigned int)info.screen());
				widgetHandle += ":";
				widgetHandle += Ogre::StringConverter::toString((unsigned long)q_parent->winId());
				//widgethandle += ":";
				//widgethandle += Ogre::StringConverter::toString((unsigned long)(info.visual()));
 
				viewConfig["parentWindowHandle"] = widgetHandle;
		#endif
 
 
		#if defined(Q_WS_MAC)

			widgetHandle = Ogre::StringConverter::toString((unsigned int)(winId()));
			viewConfig["macAPI"] = "cocoa";
			viewConfig["macAPICocoaUseNSView"] = "true";

		#endif
			Ogre::LogManager::getSingleton().logMessage("Creating Ogre subwindow '"+std::string(objectName().toAscii().data())+"'");
			mRenderWindow = mRoot->createRenderWindow(objectName().toAscii().data(),
				width(), height(), false, &viewConfig);
		}
		catch( std::exception & e )
		{
			throw std::runtime_error(std::string("Cannot initialise Ogre::RenderWindow because: ")+e.what());
		}
		try
		{
			mCamera = mSceneManager->createCamera(std::string(objectName().toAscii().data())+"_Camera");
 
			mViewport = mRenderWindow->addViewport(mCamera);
			mViewport->setBackgroundColour(Ogre::ColourValue(0,0,0));

			mCamera->setAspectRatio(Ogre::Real(width()) / Ogre::Real(height()));
			mCamera->setPosition( 100, 0, 100 );
			mCamera->lookAt( 0,0,0 );
			mCamera->setNearClipDistance( 0.01 );
		}
		catch( std::exception & e )
		{
			throw std::runtime_error(std::string("Cannot create scene because: ")+e.what());
		}
	}

	void shutdown()
	{
		if(mRoot)
		{
			for( auto i=mListeners.begin(); i!=mListeners.end(); i++ ) 
				if( (*i) )
				{
					mRoot->removeFrameListener( (*i) );
					(*i)->destroyScene();
				}
			mListeners.clear();
		}
		if(mRenderWindow)
		{
			mRenderWindow->removeAllViewports();
		}
		if(mRoot)
		{
			mRoot->detachRenderTarget(mRenderWindow);
 
			if(mSceneManager)
			{
				mRoot->destroySceneManager(mSceneManager);
			}
			mRoot = NULL;
		}
	}
	
	void moveEvent(QMoveEvent *e)
	{
		QWidget::moveEvent(e);
 
		if(e->isAccepted() && mRenderWindow)
		{
			mRenderWindow->windowMovedOrResized();
			update();
		}
	}
	void resizeEvent(QResizeEvent *e)
	{
		QWidget::resizeEvent(e);
 
		if(e->isAccepted()&&e)
		{
			const QSize &newSize = e->size();
			if(mRenderWindow)
			{
				mRenderWindow->resize(newSize.width(), newSize.height());
				mRenderWindow->windowMovedOrResized();
			}
			if(mCamera)
			{
				Ogre::Real aspectRatio = Ogre::Real(newSize.width()) / Ogre::Real(newSize.height());
				mCamera->setAspectRatio(aspectRatio);
			}
		}
	}
	
	QPaintEngine * paintEngine() const
	{
		return 0;
	}
 
	void paintEvent(QPaintEvent *e)
	{
	//    mogreRoot->_fireFrameStarted();
	//    mogreRenderWindow->update();
	//    mogreRoot->_fireFrameEnded();
 
	//	mogreRoot->renderOneFrame();
	//	mogreRenderWindow->update();
 
	//    e->accept();
		if( !mRoot ) return;
		if( !mRenderWindow ) return;
		try
		{
			mRoot->_fireFrameStarted();
			mRenderWindow->update();
			mRoot->_fireFrameRenderingQueued();
			mRoot->_fireFrameEnded();
		
			update();
		}
		catch( std::exception& e )
		{
			if( QMessageBox::question(this, "Rendering Error", (std::string("cannot render frame because: ")+e.what()).c_str(), QMessageBox::Retry|QMessageBox::Abort) == QMessageBox::Retry )
			{
			}
			else
			{
				throw;
			}
		}
		catch( ... )
		{
			if( QMessageBox::question(this, "Rendering Error", "Unknown error while rendering frame", QMessageBox::Retry|QMessageBox::Abort) == QMessageBox::Retry )
			{
			}
			else
			{
				throw;
			}
		}
	}
 

};

#endif